package com.jf.nep.shader;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.util.glu.GLU;

import com.jf.nep.render.Vertex;

public class ShaderStoreImpl implements ShaderStore {
    
    private Integer defaultVertexShaderId = null;
    private Integer defaultFragmentShaderId = null;

    private Map<String, ShaderProgram> shaderPrograms = new HashMap<String, ShaderProgram>();
    private Set<Integer> vertexShaders = new HashSet<Integer>();
    private Set<Integer> fragmentShaders = new HashSet<Integer>();
    private ShaderProgram defaultShader;
    
    public void create() throws Exception {
        try {
            // Load the vertex shader
            defaultVertexShaderId = ShaderLoader.loadShader("default_vertex_shader.glsl", GL20.GL_VERTEX_SHADER);
            vertexShaders.add(defaultVertexShaderId);
            // Load the fragment shader
            defaultFragmentShaderId = ShaderLoader.loadShader("default_fragment_shader.glsl", GL20.GL_FRAGMENT_SHADER);
            fragmentShaders.add(defaultFragmentShaderId);
            
            defaultShader = compileProgram(defaultVertexShaderId, defaultFragmentShaderId);
            shaderPrograms.put("default", defaultShader);        
            
        } catch (Exception e) {
            e.printStackTrace();
            destroyShaders();
            throw e;
        }
    }
    
    public void createShaderProgram(String id) throws Exception {
        try {
            if (shaderPrograms.containsKey(id)) {
                System.err.println("Warning, overwriting program id " + id);
                destroyProgram(shaderPrograms.get(id));
                shaderPrograms.remove(id);
            }
            
            ShaderProgram program = compileProgram(defaultVertexShaderId, defaultFragmentShaderId);

            shaderPrograms.put(id, program);
        } catch (Exception e) {
            e.printStackTrace();
            destroyShaders();
            throw e;
        }
    }
    
    private static ShaderProgram compileProgram(int vertexShader, int fragmentShader) throws Exception {
        ShaderProgram program = new ShaderProgram();
        int pid = GL20.glCreateProgram();
        
        program.setProgramId(pid);
        GL20.glAttachShader(pid, vertexShader);
        program.setVertexShaderId(vertexShader);
        GL20.glAttachShader(pid, fragmentShader);
        program.setFragmentShaderId(fragmentShader);
        GL20.glLinkProgram(pid);

        for (int i = 0; i < Vertex.SHADER_ATTRIBUTES.length; i++) {
            GL20.glBindAttribLocation(pid, i, Vertex.SHADER_ATTRIBUTES[i]);
        }

        program.setColorVectorLocation(GL20.glGetUniformLocation(pid, "colorVector"));
        program.setLightVectorLocation(GL20.glGetUniformLocation(pid, "lightVector"));
        program.setProjectionMatrixLocation(GL20.glGetUniformLocation(pid,  "projectionMatrix"));
        program.setViewMatrixLocation(GL20.glGetUniformLocation(pid, "viewMatrix"));
        program.setModelMatrixLocation(GL20.glGetUniformLocation(pid, "modelMatrix"));
        program.setNormalMatrixLocation(GL20.glGetUniformLocation(pid, "normalMatrix"));

        GL20.glValidateProgram(pid);

        int errorCheckValue = GL11.glGetError();
        if (errorCheckValue != GL11.GL_NO_ERROR) {
            throw new Exception("ERROR - Could not create the shaders:" + GLU.gluErrorString(errorCheckValue));
        }
        return program;
    }
    
    private void destroyProgram(ShaderProgram shader) {
        GL20.glDetachShader(shader.getProgramId(), shader.getVertexShaderId());
        GL20.glDetachShader(shader.getProgramId(), shader.getFragmentShaderId());
        GL20.glDeleteProgram(shader.getProgramId());
    }
    public void destroyShaders() {
        GL20.glUseProgram(0);
        
        for (ShaderProgram shaderProgram : shaderPrograms.values()) {
            destroyProgram(shaderProgram);
        }
        shaderPrograms.clear();
        for (Integer shader : vertexShaders) {
            GL20.glDeleteShader(shader);
        }
        vertexShaders.clear();
        for (Integer shader : fragmentShaders) {
            GL20.glDeleteShader(shader);
        }
        fragmentShaders.clear();
    }

    @Override
    public ShaderProgram getShaderProgram(String meshId) {
        ShaderProgram shaderProgram = shaderPrograms.get(meshId);
        if (shaderProgram != null) {
            return shaderProgram;
        }
        return defaultShader;
    }

    @Override
    public Collection<ShaderProgram> getShaderPrograms() {
        return shaderPrograms.values();
    }
    
    

}
